/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.news.view;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.math.BigDecimal;

/**
 * 自定义水波纹效果布局类
 */
public class RippleView extends DirectionalLayout implements Component.DrawTask, Component.TouchEventListener {
    private int frameRate = 10;
    private int rippleDuration = 800;
    private float rippleAlpha = 0.9f;
    private boolean animationRunning = false;
    private int zoomDuration = 200;
    private float zoomScale = 1.03f;
    private AnimatorValue rippleAnimation;
    private float ripplePose;
    private boolean hasToZoom;
    private boolean isCentered;
    private int rippleType = 0;
    private Paint paint;
    private int rippleColor = Color.GRAY.getValue();
    private int ripplePadding;

    private float touchX;
    private float touchY;

    private boolean downPose = false;
    private float downX;
    private float downY;
    private long downTime;

    private OnRippleCompleteListener onCompletionListener;

    public RippleView(Context context) {
        super(context);
    }

    public RippleView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(attrSet);
    }

    public RippleView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    private void init(AttrSet attrs) {
        rippleColor = AttrUtils.getColor(attrs, "rv_color", rippleColor);
        rippleType = AttrUtils.getInteger(attrs, "rv_type", 0);
        hasToZoom = AttrUtils.getBoolean(attrs, "rv_zoom", false);
        isCentered = AttrUtils.getBoolean(attrs, "rv_centered", false);
        rippleDuration = AttrUtils.getInteger(attrs, "rv_rippleDuration", rippleDuration);
        rippleAlpha = AttrUtils.getFloat(attrs, "rv_alpha", rippleAlpha);
        ripplePadding = AttrUtils.getDimension(attrs, "rv_ripplePadding", 0);
        zoomScale = AttrUtils.getFloat(attrs, "rv_zoomScale", 1.03f);
        zoomDuration = AttrUtils.getInteger(attrs, "rv_zoomDuration", 200);

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(new Color(rippleColor));

        setTouchEventListener(this);
        addDrawTask(this);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        boolean result = true;
        if (touchEvent.getPointerCount() == 1 && touchEvent.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
            onTouchDown(touchEvent);
        } else if (touchEvent.getPointerCount() == 1 && touchEvent.getAction() == TouchEvent.POINT_MOVE) {
            onTouchMove(touchEvent);
        } else if (touchEvent.getPointerCount() == 1 && touchEvent.getAction() == TouchEvent.PRIMARY_POINT_UP) {
            onTouchUp(touchEvent);
        } else {
            result = false;
        }
        return result;
    }

    private void onTouchDown(TouchEvent touchEvent) {
        downX = getTouchX(touchEvent, 0);
        downY = getTouchY(touchEvent, 0);
        downTime = System.currentTimeMillis();
        downPose = true;
        createAnimation(getTouchX(touchEvent, 0), getTouchY(touchEvent, 0));
    }

    private void onTouchMove(TouchEvent touchEvent) {
        if (downPose && Math.abs(BigDecimal.valueOf(getTouchX(touchEvent, 0))
                .subtract(BigDecimal.valueOf(downX)).floatValue()) < dp2px(50)
                && Math.abs(BigDecimal.valueOf(getTouchY(touchEvent, 0))
                .subtract(BigDecimal.valueOf(downY)).floatValue()) < dp2px(50)) {
            if (System.currentTimeMillis() - downTime > 500) {
                downPose = false;
            }
        } else {
            downPose = false;
        }
    }

    private void onTouchUp(TouchEvent touchEvent) {
        downPose = false;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        float radiusMax;
        if (getWidth() == 0 || getHeight() == 0) {
            return;
        } else {
            radiusMax = (float) Math.sqrt(getWidth() * getWidth() + getHeight() * getHeight());
            if (rippleType != 2) {
                radiusMax /= 2;
            }
            radiusMax = BigDecimal.valueOf(radiusMax).subtract(BigDecimal.valueOf(ripplePadding)).floatValue();
        }

        if (isCentered || rippleType == 1) {
            touchX = getWidth() / 2.0f;
            touchY = getHeight() / 2.0f;
        }
        BigDecimal a1 = BigDecimal.valueOf(rippleAlpha).multiply(BigDecimal.valueOf(ripplePose));
        paint.setAlpha(BigDecimal.valueOf(rippleAlpha).subtract(a1).floatValue());

        float width = 0;
        if (rippleType == 1 && ripplePose > 0.4f) {
            paint.setStyle(Paint.Style.STROKE_STYLE);
            BigDecimal temp1 = BigDecimal.valueOf(ripplePose).subtract(BigDecimal.valueOf(0.4f));
            BigDecimal temp2 = BigDecimal.valueOf(radiusMax)
                    .multiply(temp1).divide(BigDecimal.valueOf(0.6f), BigDecimal.ROUND_HALF_UP);
            width = BigDecimal.valueOf(radiusMax)
                    .multiply(BigDecimal.valueOf(ripplePose)).subtract(temp2).floatValue();
            paint.setStrokeWidth(width);
        } else {
            paint.setStyle(Paint.Style.FILL_STYLE);
        }
        BigDecimal temp1 = BigDecimal.valueOf(radiusMax).multiply(BigDecimal.valueOf(ripplePose));
        BigDecimal temp2 = BigDecimal.valueOf(width).divide(BigDecimal.valueOf(2), BigDecimal.ROUND_HALF_UP);
        canvas.drawCircle(touchX, touchY, temp1.subtract(temp2).floatValue(), paint);
    }

    private void scaleAnimation() {
        AnimatorProperty scaleAnimation1 = new AnimatorProperty(this);
        scaleAnimation1.setDuration(zoomDuration);
        scaleAnimation1.scaleX(zoomScale).scaleY(zoomScale);
        AnimatorProperty scaleAnimation2 = new AnimatorProperty(this);
        scaleAnimation2.setDuration(zoomDuration);
        scaleAnimation2.scaleX(1.0f).scaleY(1.0f);
        final AnimatorGroup animatorGroup = new AnimatorGroup();
        animatorGroup.build().addAnimators(scaleAnimation1).addAnimators(scaleAnimation2);
        animatorGroup.start();
    }

    private void createAnimation(final float touchX, final float touchY) {
        if (this.isEnabled() && !animationRunning) {
            if (hasToZoom) {
                scaleAnimation();
            }
            if (rippleAnimation == null) {
                rippleAnimation = new AnimatorValue();
                rippleAnimation.setDuration(rippleDuration);
                rippleAnimation.setValueUpdateListener(
                        (animatorValue, value) -> {
                            ripplePose = value;
                            invalidate();
                        });
                rippleAnimation.setStateChangedListener(
                        new Animator.StateChangedListener() {
                            @Override
                            public void onStart(Animator animator) {
                            }

                            @Override
                            public void onStop(Animator animator) {
                            }

                            @Override
                            public void onCancel(Animator animator) {
                            }

                            @Override
                            public void onEnd(Animator animator) {
                                if (onCompletionListener != null) {
                                    onCompletionListener.onComplete(RippleView.this);
                                }
                                animationRunning = false;
                            }

                            @Override
                            public void onPause(Animator animator) {
                            }

                            @Override
                            public void onResume(Animator animator) {
                            }
                        });
            }
            if (rippleAnimation.isRunning()) {
                return;
            }
            animationRunning = true;
            this.touchX = touchX;
            this.touchY = touchY;
            rippleAnimation.start();
        }
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float tempX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                tempX = BigDecimal.valueOf(touchEvent.getPointerScreenPosition(index).getX())
                        .subtract(BigDecimal.valueOf(xy[0])).floatValue();
            } else {
                tempX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return tempX;
    }

    private float getTouchY(TouchEvent touchEvent, int index) {
        float tempY = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                tempY = BigDecimal.valueOf(touchEvent.getPointerScreenPosition(index).getY())
                        .subtract(BigDecimal.valueOf(xy[1])).floatValue();
            } else {
                tempY = touchEvent.getPointerPosition(index).getY();
            }
        }
        return tempY;
    }

    /**
     * Launch Ripple animation for the current view with a MotionEvent
     *
     * @param event MotionEvent registered by the Ripple gesture listener
     */
    public void animateRipple(TouchEvent event) {
        createAnimation(getTouchX(event, 0), getTouchY(event, 0));
    }

    /**
     * Launch Ripple animation for the current view centered at x and y position
     *
     * @param touchX Horizontal position of the ripple center
     * @param touchY Vertical position of the ripple center
     */
    public void animateRipple(final float touchX, final float touchY) {
        createAnimation(touchX, touchY);
    }

    /**
     * Set Ripple color, default is #FFFFFF
     *
     * @param rippleColor New color resource
     */
    public void setRippleColor(int rippleColor) {
        this.rippleColor = rippleColor;
    }

    public int getRippleColor() {
        return rippleColor;
    }

    public RippleType getRippleType() {
        return RippleType.values()[rippleType];
    }

    /**
     * Set Ripple type, default is RippleType.SIMPLE
     *
     * @param rippleType New Ripple type for next animation
     */
    public void setRippleType(final RippleType rippleType) {
        this.rippleType = rippleType.ordinal();
    }

    public Boolean isCentered() {
        return isCentered;
    }

    /**
     * Set if ripple animation has to be centered in its parent view or not, default is False
     *
     * @param isCentered the type is centered
     */
    public void setCentered(final Boolean isCentered) {
        this.isCentered = isCentered;
    }

    public int getRipplePadding() {
        return ripplePadding;
    }

    /**
     * Set Ripple padding if you want to avoid some graphic glitch
     *
     * @param ripplePadding New Ripple padding in pixel, default is 0px
     */
    public void setRipplePadding(int ripplePadding) {
        this.ripplePadding = ripplePadding;
    }

    public Boolean isZooming() {
        return hasToZoom;
    }

    /**
     * At the end of Ripple effect, the child views has to zoom
     *
     * @param hasToZoom Do the child views have to zoom ? default is False
     */
    public void setZooming(Boolean hasToZoom) {
        this.hasToZoom = hasToZoom;
    }

    public float getZoomScale() {
        return zoomScale;
    }

    /**
     * Scale of the end animation
     *
     * @param zoomScale Value of scale animation, default is 1.03f
     */
    public void setZoomScale(float zoomScale) {
        this.zoomScale = zoomScale;
    }

    public int getZoomDuration() {
        return zoomDuration;
    }

    /**
     * Duration of the ending animation in ms
     *
     * @param zoomDuration Duration, default is 200ms
     */
    public void setZoomDuration(int zoomDuration) {
        this.zoomDuration = zoomDuration;
    }

    public int getRippleDuration() {
        return rippleDuration;
    }

    /**
     * Duration of the Ripple animation in ms
     *
     * @param rippleDuration Duration, default is 400ms
     */
    public void setRippleDuration(int rippleDuration) {
        this.rippleDuration = rippleDuration;
    }

    public int getFrameRate() {
        return frameRate;
    }

    /**
     * Set framerate for Ripple animation
     *
     * @param frameRate New framerate value, default is 10
     */
    public void setFrameRate(int frameRate) {
        this.frameRate = frameRate;
    }

    public float getRippleAlpha() {
        return rippleAlpha;
    }

    /**
     * Set alpha for ripple effect color
     *
     * @param rippleAlpha Alpha value between 0 and 255, default is 90
     */
    public void setRippleAlpha(float rippleAlpha) {
        this.rippleAlpha = rippleAlpha;
    }

    public void setOnRippleCompleteListener(OnRippleCompleteListener listener) {
        this.onCompletionListener = listener;
    }

    /**
     * Defines a callback called at the end of the Ripple effect
     */
    public interface OnRippleCompleteListener {
        void onComplete(RippleView rippleView);
    }

    public enum RippleType {
        SIMPLE(0),
        DOUBLE(1),
        RECTANGLE(2);

        int type;

        RippleType(int type) {
            this.type = type;
        }
    }

    private int dp2px(int dp) {
        return (int) (getResourceManager().getDeviceCapability().screenDensity / 160 * dp);
    }
}
